﻿using M.Core.RmoteCall.Log;
using M.Core.Utils;
using Microsoft.Extensions.Caching.Memory;
using Polly;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
 
namespace M.Core.RmoteCall
{
    public class CallStrategy
    {
        static CallStrategy callStrategy = new CallStrategy();

        public static CallStrategy GetInstance()
        {
            return callStrategy;
        }

        /// <summary>
        /// 最多重试几次，如果为0则不重试
        /// </summary>
        public int MaxRetryTimes { get; set; } = 0;

        /// <summary>
        /// 重试间隔的毫秒数
        /// </summary>
        public int RetryIntervalMilliseconds { get; set; } = 100;

        /// <summary>
        /// 是否启用熔断
        /// </summary>
        public bool IsEnableCircuitBreaker { get; set; } = true;

        /// <summary>
        /// 熔断前出现允许错误几次
        /// </summary>
        public int ExceptionsAllowedBeforeBreaking { get; set; } = 3;

        /// <summary>
        /// 熔断多长时间（毫秒）
        /// </summary>
        public int MillisecondsOfBreak { get; set; } = 1000;

        /// <summary>
        /// 执行超过多少毫秒则认为超时（0表示不检测超时）
        /// </summary>
        public int TimeOutMilliseconds { get; set; } = 0;

        /// <summary>
        /// 缓存多少毫秒（0表示不缓存），用“类名+方法名+所有参数ToString拼接”做缓存Key
        /// </summary>

        public int CacheTTLMilliseconds { get; set; } = 0;

        private static ConcurrentDictionary<MethodInfo, Policy<Object>> policies
            = new ConcurrentDictionary<MethodInfo, Policy<Object>>();

        private static readonly IMemoryCache memoryCache
            = new MemoryCache(new Microsoft.Extensions.Caching.Memory.MemoryCacheOptions());


        public CallStrategy()
        {

        }

        //public string FallBackMethod { get; set; }

        public dynamic InvokeRpc(ICallHandler callHandler, Type IType, MethodInfo targetMethod, object[] args)
        {
            Object returnObj = null;

            policies.TryGetValue(targetMethod, out Policy<Object> policy);
            lock (policies)//因为Invoke可能是并发调用，因此要确保policies赋值的线程安全
            {
                if (policy == null)
                {
                    policy = Policy.NoOpAsync<Object>();//创建一个空的Policy
                    if (IsEnableCircuitBreaker)
                    {
                        policy = policy.WrapAsync(Policy.Handle<Exception>().CircuitBreakerAsync(ExceptionsAllowedBeforeBreaking, TimeSpan.FromMilliseconds(MillisecondsOfBreak)));
                    }
                    if (TimeOutMilliseconds > 0)
                    {
                        policy = policy.WrapAsync(Policy.TimeoutAsync(() => TimeSpan.FromMilliseconds(TimeOutMilliseconds), Polly.Timeout.TimeoutStrategy.Pessimistic));
                    }
                    if (MaxRetryTimes > 0)
                    {
                        policy = policy.WrapAsync(Policy.Handle<Exception>().WaitAndRetryAsync(MaxRetryTimes, i => TimeSpan.FromMilliseconds(RetryIntervalMilliseconds)));
                    }

                    Policy<object> policyFallBack = Policy<object>
                    .Handle<Exception>((ex)=> {
                        CallLog.GetInstance().Logger(ex, "调用失败 targetMethod:"+ targetMethod.DeclaringType
                                                                   + "." + targetMethod + string.Join("_", args));
                        return false;
                    })
                    .FallbackAsync((t) =>
                   {
                       //调用降级调用方法？
                       return Task.FromResult<object>(null);
                    });

                    policy = policyFallBack.WrapAsync(policy);
                    //放入
                    policies.TryAdd(targetMethod, policy);
                }
            }

            //把本地调用的AspectContext传递给Polly，主要给FallbackAsync中使用，避免闭包的坑
            Context pollyCtx = new Context();
            //pollyCtx["aspectContext"] = context;

            ////Install-Package Microsoft.Extensions.Caching.Memory
            if (CacheTTLMilliseconds > 0)
            {
                //用类名+方法名+参数的下划线连接起来作为缓存key
                string cacheKey = "CallStrategyMethodCacheManager_Key_" + targetMethod.DeclaringType
                                                                   + "." + targetMethod + string.Join("_", args);
                //尝试去缓存中获取。如果找到了，则直接用缓存中的值做返回值
                if (memoryCache.TryGetValue(cacheKey, out var cacheValue))
                {
                    returnObj = cacheValue;
                }
                else
                {
                    //如果缓存中没有，则执行实际被拦截的方法
                    returnObj = policy.ExecuteAsync((content) => Task.FromResult<object>(callHandler.Process(IType, targetMethod, args)), pollyCtx).GetAwaiter().GetResult();
                    //存入缓存中
                    using (var cacheEntry = memoryCache.CreateEntry(cacheKey))
                    {
                        cacheEntry.Value = returnObj;
                        cacheEntry.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMilliseconds(CacheTTLMilliseconds);
                    }
                }
            }
            else//如果没有启用缓存，就直接执行业务方法
            {
                returnObj = policy.ExecuteAsync((content) => Task.FromResult<object>(callHandler.Process(IType, targetMethod, args)), pollyCtx).GetAwaiter().GetResult();
            }

            return returnObj;
        }

        internal string InvokeApi(Type type, MethodInfo targetMethod, string method, string url, string querys)
        {
            var json = string.Empty;

            policies.TryGetValue(targetMethod, out Policy<object> policy);
            lock (policies)//因为Invoke可能是并发调用，因此要确保policies赋值的线程安全
            {
                if (policy == null)
                {
                    policy = Policy.NoOpAsync<object>();
                    if (IsEnableCircuitBreaker)
                    {
                        policy = policy.WrapAsync(Policy.Handle<Exception>().CircuitBreakerAsync(ExceptionsAllowedBeforeBreaking, TimeSpan.FromMilliseconds(MillisecondsOfBreak)));
                    }
                    if (TimeOutMilliseconds > 0)
                    {
                        policy = policy.WrapAsync(Policy.TimeoutAsync(() => TimeSpan.FromMilliseconds(TimeOutMilliseconds), Polly.Timeout.TimeoutStrategy.Pessimistic));
                    }
                    if (MaxRetryTimes > 0)
                    {
                        policy = policy.WrapAsync(Policy.Handle<Exception>().WaitAndRetryAsync(MaxRetryTimes, i => TimeSpan.FromMilliseconds(RetryIntervalMilliseconds)));
                    }
                    Policy<object> policyFallBack = Policy<object>
                    .Handle<Exception>((ex) => {
                        CallLog.GetInstance().Logger(ex, "调用失败 targetMethod:" + targetMethod.DeclaringType
                                                                   + "." + targetMethod + querys);
                        return false;
                    })
                    .FallbackAsync((t) =>
                    {
                        //调用降级调用方法？
                        return Task.FromResult<Object>(null);
                    });

                    policy = policyFallBack.WrapAsync(policy);
                    //放入
                    policies.TryAdd(targetMethod, policy);
                }
            }

            //把本地调用的AspectContext传递给Polly，主要给FallbackAsync中使用，避免闭包的坑
            Context pollyCtx = new Context();
            //pollyCtx["aspectContext"] = context;

            ////Install-Package Microsoft.Extensions.Caching.Memory
            if (CacheTTLMilliseconds > 0)
            {
                //用类名+方法名+参数的下划线连接起来作为缓存key
                string cacheKey = "CallStrategyMethodCacheManager_Key_" + targetMethod.DeclaringType
                                                                   + "." + targetMethod + querys;
                //尝试去缓存中获取。如果找到了，则直接用缓存中的值做返回值
                if (memoryCache.TryGetValue(cacheKey, out var cacheValue))
                {
                    json = (string)cacheValue;
                }
                else
                {
                    //如果缓存中没有，则执行实际被拦截的方法
                    json = (string)policy.ExecuteAsync(ctx =>
                {
                    return Task.FromResult<object>(GetResult(method, url, querys));
                
                }, pollyCtx).GetAwaiter().GetResult();
                    //存入缓存中
                    using (var cacheEntry = memoryCache.CreateEntry(cacheKey))
                    {
                        cacheEntry.Value = json;
                        cacheEntry.AbsoluteExpiration = DateTime.Now + TimeSpan.FromMilliseconds(CacheTTLMilliseconds);
                    }
                }
            }
            else//如果没有启用缓存，就直接执行业务方法
            {
                json = (string)policy.ExecuteAsync(ctx =>
                {
                    return Task.FromResult<object>(GetResult(method, url, querys));
                }, pollyCtx).GetAwaiter().GetResult();
            }
            return json;
        }

        private string GetResult(string m,string url,string querys)
        {
            switch (m)
            {
                default:
                case "GET":
                    return HttpUtil.HttpWebRestfulGet(url, querys);
                case "POST":
                    return HttpUtil.HttpWebRequestPost(url, querys);
            }
        }

    }
}
